/*
 * @Author: your name
 * @Date: 2020-07-22 22:13:45
 * @LastEditTime2020-11-21 23:50:31
 * @LastEditorsPlease set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \STM32F103_FreeRTOS\User\app\shell\shell.c
 */ 
#include "shell.h"
#include <string.h>
#include <stdio.h>
// #include "task.h"
// #include "queue.h"
#include "bsp_uart.h"

/* shell task definition */
// StaticTask_t shell_task_tcb;
// StackType_t shell_task_stack[SHELL_TASK_STACK_SIZE];

/* shell main operation */
static void shell_init(Shell_t *shell);
static void shell_start_init(void);
static void shell_receive(uint8_t *buffer, uint16_t *old_pos);
static uint8_t shell_read(uint8_t *data);
static uint8_t shell_write(uint8_t data);
static uint8_t shell_handler(struct Shell *shell, uint8_t data);

/* Key function */
static void shell_backspace(Shell_t *shell);
static void shell_enter(Shell_t *shell, char ch);
static void shell_left(Shell_t *shell);
static void shell_right(Shell_t *shell);
static void shell_up(Shell_t *shell);
static void shell_down(Shell_t *shell);
static void shell_tab(Shell_t *shell);
static void shell_normal(Shell_t *shell, char ch);

/* Shell service function */
static void shell_prompt_print(void);
static void shell_push_history(Shell_t *shell);
static void shell_change_history(Shell_t *shell, uint8_t direction);
static void shell_disp_string(Shell_t *shell, char *str, uint16_t len);

// SemaphoreHandle_t ShellPrintCmdSemapBinary = NULL;
// QueueHandle_t g_shell_queue = NULL;
Shell_t g_Shell;

/**
 * @brief : Shell Task
 * @param : void *para
 * @retval: void
 * @return: 
 */
void shell_task(void *para)
{
    // TickType_t xShell_LastExecutionTime;
    // const TickType_t xShell_BlockPeriod = pdMS_TO_TICKS(2);
    // xShell_LastExecutionTime = xTaskGetTickCount();
    shell_init(&g_Shell);
    for(;;)
    {
        // vTaskDelayUntil(&xShell_LastExecutionTime, xShell_BlockPeriod);
        shell_receive(debug_uart_rx.rx_buffer, &debug_uart_rx.old_pos);
    }
}

/**
 * @brief  Shell process the receive data, push the data to the shell_rx_queue
 * @param  buffer
 * @param  curr_pos
 * @param  old_pos
 * @retval 
 */
static void shell_receive(uint8_t *buffer, uint16_t *old_pos)
{
    uint16_t index = 0;
    uint16_t position = 0;
    uint16_t old_position = 0;

    old_position = *old_pos;
    position = uart_curr_pos_get();
    *old_pos = position;
    if(position != old_position)
    {
        if(position > old_position)
        {
            /* Dma data transfer is not overflow */
            for(index = old_position; index < position; ++index)
                shell_handler(&g_Shell, buffer[index]);
        }
        else
        {
            /* Dma data transfer is overflow */
            for(index = old_position; index < DEBUG_UART_RX_BUFFER_SIZE; ++index)
                shell_handler(&g_Shell, buffer[index]);
            for(index = 0; index < position; ++index)
                shell_handler(&g_Shell, buffer[index]);
        }
    }
}

/**
 * @brief: 
 * @param {type}  
 * @retval: 
 * @return: 
 */
uint8_t shell_handler(Shell_t *shell, uint8_t data)
{
    const char ch = (char)data;
    switch(shell->state)
    {
        case 0:
            if(ch == 0x1b) 
                shell->state = 1u;
            else if(ch == '\0' || ch == 0xFF)
                ;/* null or error */
            else if(ch == '\t') /* tab */
                shell_tab(shell);
            else if(ch == 0x08 || ch == 0x7f) /* backspace */
                shell_backspace(shell);
            else if(ch == '\r' || ch == '\n') /* enter */
                shell_enter(shell, ch);
            else /* normal char */
                shell_normal(shell, ch);
        break;
        case 1:
            if(ch == 0x5b) 
                shell->state = 2u;
            else 
                shell->state = 0u;
        break;
        case 2:
            if(ch == 0x41) /* up */
                shell_up(shell);
            else if(ch == 0x42) /* down */
                shell_down(shell);
            else if(ch == 0x44) /* left */
                shell_left(shell);
            else if(ch == 0x43) /* right */
                shell_right(shell);
            else
                shell->state = 0u;
            shell->state = 0u;
        break;
        case 3u:

        break;

        default:
        break;
    }
    return TRUE;
}

/**
 * @brief: 
 * @param {type}  
 * @retval: 
 * @return: 
 */
void shell_init(Shell_t *shell)
{
    shell->state        = 0;
    shell->line_curpos  = 0;
    shell->line_endpos  = 0;
    shell->history_tail = SHELL_HISTORY_SIZE;
    shell->history_num  = 0;
    shell->history_curr = 0;
    shell->read         = shell_read;
    shell->write        = shell_write;
    shell_cmd_init();
    shell_start_init();
    shell_prompt_print();
    //g_shell_queue = xQueueCreate(10, sizeof(void *));
}

static void shell_start_init(void)
{
    printf("  _______           _______  _        _        \r\n");
    printf(" (  ____ \\|\\     /|(  ____ \\( \\      ( \\       \r\n");
    printf(" | (    \\/| )   ( || (    \\/| (      | (       \r\n");
    printf(" | (_____ | (___) || (__    | |      | |       \r\n");
    printf(" (_____  )|  ___  ||  __)   | |      | |       \r\n");
    printf("       ) || (   ) || (      | |      | |       \r\n");
    printf(" /\\____) || )   ( || (____/\\| (____/\\| (____/\\ \r\n");
    printf(" \\_______)|/     \\|(_______/(_______/(_______/ \r\n");
}

/**
 * @brief  : Print the prompt of this shell
 * @param  : void
 * @retval : void
 * @return : 
 */
static void shell_prompt_print(void)
{
    printf("%s", SHELL_PROMPT);
}

/**
 * @brief  : Handle the backspace key
 * @param  : {Shell_t *} shell, the shell pointer
 * @retval : void
 * @return : 
 */
static void shell_backspace(Shell_t *shell)
{
    if((shell->line_endpos == 0) || (shell->line_curpos == 0))
        return;

    uint16_t index = 0;
    if(shell->line_endpos > shell->line_curpos)
    {
        memcpy(&shell->line[shell->line_curpos - 1], 
                &shell->line[shell->line_curpos],
                shell->line_endpos - shell->line_curpos);
        shell->line_curpos--;
        shell->line_endpos--;

        shell->write('\b');
        for(index = shell->line_curpos; index < shell->line_endpos; ++index)
            shell->write(shell->line[index]);
        shell->write(' ');
        shell->write('\b');
        for(index = shell->line_curpos; index < shell->line_endpos; ++index)
            shell->write('\b');
    }
    else
    {
        shell->line_curpos--;
        shell->line_endpos--;
        shell->write('\b');
        shell->write(' ');
        shell->write('\b');
    }
}

/**
 * @brief  : Handle the enter key
             1. Push the command to history;
             2. Find the command in command list;
             3. If command is valid, excute the command;
 * @param  : {Shell_t *} shell, the shell pointer
 * @retval : void
 * @return : 
 */
static void shell_enter(Shell_t *shell, char ch)
{
    printf("\r\n");
    /* History handler */
    if(shell->line_endpos > 0)
    {                
        shell_push_history(shell);
        shell_cmd_func cmd_func = shell_cmd_find(&shell->line[0], shell->line_endpos);
        if(cmd_func != NULL)
            cmd_func();
        else
            printf("Invalid command!\r\n");
        
        shell->line_curpos = 0;
        shell->line_endpos = 0;
    }
    /* change line or enter */
    shell_prompt_print();
}

/**
 * @brief  : Handle the left arrow key, move the cursor left
 * @param  : {Shell_t *} shell, the shell pointer
 * @retval : void
 * @return : 
 */
static void shell_left(Shell_t *shell)
{
    if(shell->line_curpos > 0)
    {
        shell->write('\b');
        shell->line_curpos--;
    }
}

/**
 * @brief  : Handle the right arrow key, move the cursor right
 * @param  : {Shell_t *} shell, the shell pointer
 * @retval : void
 * @return : 
 */
static void shell_right(Shell_t *shell)
{
    if(shell->line_curpos < shell->line_endpos)
    {
        shell->write(shell->line[shell->line_curpos]);
        shell->line_curpos++;
    }
}

/**
 * @brief: Handle the down arrow key, display the previous history command
 * @param {Shell_t *} shell 
 * @retval: void
 * @return: 
 */
static void shell_up(Shell_t *shell)
{
    shell_change_history(shell, 1);
    shell_disp_string(shell, &shell->history[shell->history_curr][0], 
        strlen(&shell->history[shell->history_curr][0]));
}

/**
 * @brief: Handle the down arrow key, display the next history command
 * @param[in]: {Shell_t *} shell
 * @retval: void
 * @return: 
 */
static void shell_down(Shell_t *shell)
{
    shell_change_history(shell, 0);
    shell_disp_string(shell, &shell->history[shell->history_curr][0], 
        strlen(&shell->history[shell->history_curr][0]));
}

/**
 * @brief: Handle the tab key, auto complete the command
 * @param[in]: {Shell_t *} shell
 * @retval: void
 * @return: 
 */
static void shell_tab(Shell_t *shell)
{
    char *cmd_str = NULL;
    if(shell->line_endpos == 0)
        return;
    /* Auto complete the command */
    if(shell_cmd_match(&shell->line[0], shell->line_endpos, &cmd_str))
    {
        shell_disp_string(shell, cmd_str, strlen(cmd_str));
    }
}

/**
 * @brief: 
 * @param {type}  
 * @retval: 
 * @return: 
 */
static void shell_normal(Shell_t *shell, char ch)
{
    if(shell->line_endpos == SHELL_LINE_LENGTH)
        return;

    uint16_t index = 0;
    if(shell->line_endpos > shell->line_curpos)
    {
        /* Shift the data 1 position */
        for(index = shell->line_endpos; index > shell->line_curpos; index--)
            shell->line[index] = shell->line[index - 1];
        shell->line[shell->line_curpos] = ch;
        shell->line_endpos++;
        /* Print the processed string */
        for(index = shell->line_curpos; index < shell->line_endpos; index++)
            shell->write(shell->line[index]);
        shell->line_curpos++;
        /* Restore the cursor position in the shell window */
        for(index = shell->line_curpos; index < shell->line_endpos; index++)
            shell->write('\b');
    }
    else
    {
        /* normal char */
        shell->line[shell->line_endpos] = ch;
        shell->line_endpos++;
        shell->line_curpos++;
        shell->write(ch);
    }
}

/**
 * @brief: 
 * @param {type}  
 * @retval: 
 * @return: 
 */
static void shell_push_history(Shell_t *shell)
{
    shell->history_tail++;
    shell->history_curr = shell->history_tail + 1;
    if(shell->history_tail >= SHELL_HISTORY_SIZE)
        shell->history_tail = 0;
    if(shell->history_curr >= SHELL_HISTORY_SIZE)
        shell->history_curr = 0;
    if(shell->history_num < SHELL_HISTORY_SIZE)
        shell->history_num++;
    /* Clear the history */
    memset(&shell->history[shell->history_tail][0], 0, SHELL_LINE_LENGTH);
    /* Set the history */
    memcpy(&shell->history[shell->history_tail][0], &shell->line[0], shell->line_endpos);
}

/**
 * @brief: 
 * @param[in]:{}
 * @retval: 
 * @return: 
 */
static void shell_change_history(Shell_t *shell, uint8_t direction)
{
    if(direction == 1) /* previous */
    {
        if(shell->history_num < SHELL_HISTORY_SIZE)
        {
            if(shell->history_curr > 0)
                shell->history_curr--;
            else
                shell->history_curr = shell->history_num - 1;
        }
        else
        {
            if(shell->history_curr > 0)
                shell->history_curr--;
            else
                shell->history_curr = SHELL_HISTORY_SIZE - 1;
        }
    }
    else /* next */
    {
        if(shell->history_num < SHELL_HISTORY_SIZE)
        {
            if(shell->history_curr < (shell->history_num - 1))
                shell->history_curr++;
            else
                shell->history_curr = 0;
        }
        else
        {
            if(shell->history_curr < (SHELL_HISTORY_SIZE - 1))
                shell->history_curr++;
            else
                shell->history_curr = 0;
        }
    }
}

/**
 * @brief: 
 * @param {type}  
 * @retval: 
 * @return: 
 */
static void shell_disp_string(Shell_t *shell, char *str, uint16_t len)
{
    uint16_t old_endpos = 0;
    uint16_t index = 0;
    
    old_endpos = shell->line_endpos;
    /* Move the cursor to the zero position */
    for(index = 0; index < shell->line_curpos; ++index)
        shell->write('\b');
    
    /* Copy the string to the shell line, and update the cursor_position and end_position */
    shell->line_curpos = shell->line_endpos = len;
    memcpy(&shell->line[0], str, shell->line_endpos);

    /* Print the shell line string */
    for(index = 0; index < shell->line_endpos; ++index)
        shell->write(shell->line[index]);

    /* If the original string is longer than current string, clear the extra part */
    if(shell->line_endpos < old_endpos)
    {
        for(index = shell->line_endpos; index < old_endpos; ++index)
            shell->write(' ');
        for(index = shell->line_endpos; index < old_endpos; ++index)
            shell->write('\b');
    }
}

static uint8_t shell_read(uint8_t *data)
{
    return FALSE;
}

static uint8_t shell_write(uint8_t data)
{
    bsp_uart_transmit(&data, 1, 0xFFFF);
    return TRUE;
}


